#!/bin/bash
#
# Subversion pre-commit hook.
# This hook transfers all changes, performing in the Subversion repository, 
# to the underlying ClearCase repository.
#
# Expected arguments:
# $1 - Path to svn repository.
# $2 - Transaction id.
# $3 - Path to ClearCase view. This argument is oprional if CC_VIEW_DIR is 
#      specified in svncc.cfg.

SVN_REPO_DIR="$1"
TXN="$2"
LOG_FILE_SUFFIX="_$2.log"
SCRIPT_PATH=${SCRIPT_PATH:-"$0"}
. "`dirname "$SCRIPT_PATH"`/common.sh"
SYNC_LOCK="$SVN_REPO_DIR/svncc/sync.lock"

if [ -e "$SYNC_LOCK" ]
then
    exit 0
fi

############################### Declarations ###################################
alias log="log_message "\$FUNCNAME:\$LINENO""
alias exec_cmd="execute_command "\$FUNCNAME:\$LINENO""
alias try_exec_cmd="try_execute_command "\$FUNCNAME:\$LINENO""
alias cc="exec_cmd '"$CLEARTOOL_CMD"'"
log "`date`| Executing pre-commit scrit: $SCRIPT_PATH"

CC_VIEW_DIR=${CC_VIEW_DIR:-"$3"}
DELETE_LOG_ON_SUCCESS="$DELETE_PRE_COMMIT_LOG_ON_SUCCESS"
DELETE_LOG_ON_FAILURE="$DELETE_PRE_COMMIT_LOG_ON_FAILURE"

if [ ! -z $IS_CYGWIN ]
then
    SVN_REPO_DIR=`exec_cmd cygpath -m "$SVN_REPO_DIR"`
fi

COMMENT=`try_exec_cmd "$SVNLOOK_CMD" log -t "$TXN" "$SVN_REPO_DIR"`
declare -a SVN_CHANGED
declare -a SVN_NEW_FILES
declare -a SVN_NEW_DIRS
declare -a SVN_DELETED

declare -a CC_TOBE_CHANGED
declare -a CC_TOBE_CREATED_FILES
declare -a CC_TOBE_CREATED_DIRS
declare -a CC_TOBE_DELETED

declare -a CC_CHECKED_OUT_FILES
declare -a CC_CHECKED_OUT_DIRS
declare -a CC_CHECKED_IN_FILES
declare -a CC_CHECKED_IN_DIRS
declare -a CC_CREATED_FILES
declare -a CC_CREATED_DIRS
declare -a CC_DELETED

################################# Functions ####################################

function pre_exit {
    [[ ${#CC_CHECKED_OUT_FILES[@]} != 0 ]] && log "Warning! Missed checked out files: ${CC_CHECKED_OUT_FILES[@]}"
    [[ ${#CC_CHECKED_OUT_DIRS[@]} != 0 ]] && log "Warning! Missed checked out directories: ${CC_CHECKED_OUT_DIRS[@]}"
}

function rollback_and_fail {
    local ec="$EXIT_CODE"
    log "Error occurred."
    
    if [ "$ROLLBACK_ON_FAILURE" != "true" ]
    then
        log "Checked out files:"
        size=${#CC_CHECKED_OUT_DIRS[@]}
        for ((i = 0; i < $size; i++ ))
        do
            f=${CC_CHECKED_OUT_DIRS[$i]}
            if [ ! -z "$f" ] 
            then
                log "$f"
            fi
        done
        
        size=${#CC_CHECKED_OUT_FILES[@]}
        for ((i = 0; i < $size; i++ ))
        do
            f=${CC_CHECKED_OUT_FILES[$i]}
            if [ ! -z "$f" ] 
            then
                log "$f"
            fi
        done
        
        log "Checked in files:"
        size=${#CC_CHECKED_IN_DIRS[@]}
        for ((i = 0; i < $size; i++ ))
        do
            f=${CC_CHECKED_IN_DIRS[$i]}
            if [ ! -z "$f" ] 
            then
                log "$f"
            fi
        done
    
        size=${#CC_CHECKED_IN_FILES[@]}
        for ((i = 0; i < $size; i++ ))
        do
            f=${CC_CHECKED_IN_FILES[$i]}
            if [ ! -z "$f" ] 
            then
                log "$f"
            fi
        done
        
        pre_exit
        fail $ec
    fi

    log "Rolling back changes."
    local i
    local size
    [[ -z $ec || $ec == 0 ]] && ec="1"
    
    size=${#CC_CHECKED_OUT_FILES[@]}
    for ((i = 0; i < $size; i++ ))
    do
        f=${CC_CHECKED_OUT_FILES[$i]}
        if [ ! -z "$f" ] 
        then
            cc unco -rm "$f"
            [[  $EXIT_CODE == 0 ]] && unset CC_CHECKED_OUT_FILES[$i]
        fi
    done

    size=${#CC_CHECKED_OUT_DIRS[@]}
    for ((i = 0; i < $size; i++ ))
    do
        f=${CC_CHECKED_OUT_DIRS[$i]}
        if [ ! -z "$f" ] 
        then
            cc unco -rm "$f"
            [[  $EXIT_CODE == 0 ]] && unset CC_CHECKED_OUT_DIRS[$i]
        fi
    done
    
    size=${#CC_CHECKED_IN_FILES[@]}
    for ((i = 0; i < $size; i++ ))
    do
        f=${CC_CHECKED_IN_FILES[$i]}
        if [ ! -z "$f" ] 
        then
            cc rmver -f -nc "$f"
            [[  $EXIT_CODE == 0 ]] && unset CC_CHECKED_IN_FILES[$i]
        fi
    done
    
    size=${#CC_CHECKED_IN_DIRS[@]}
    for ((i = 0; i < $size; i++ ))
    do
        f=${CC_CHECKED_IN_DIRS[$i]}
        if [ ! -z "$f" ] 
        then
            cc rmver -f -nc "$f"
            [[  $EXIT_CODE == 0 ]] && unset CC_CHECKED_IN_DIRS[$i]
        fi
    done

    size=${#CC_TOBE_CREATED_DIRS[@]}
    for ((i = 0; i < $size; i++ ))
    do
        f=${CC_TOBE_CREATED_DIRS[$i]}
        [ -d "$f" ] && exec_cmd rm -rfv "$f"
    done

    size=${#CC_TOBE_CREATED_FILES[@]}
    for ((i = 0; i < $size; i++ ))
    do
        f=${CC_TOBE_CREATED_FILES[$i]}
        [ -f "$f" ] && exec_cmd rm -fv "$f"
    done
    
    [[ ${#CC_CHECKED_IN_FILES[@]} != 0 ]] && log "Warning! Failed to unversion files: ${CC_CHECKED_IN_FILES[@]}"
    [[ ${#CC_CHECKED_IN_DIRS[@]} != 0 ]] && log "Warning! Failed to unversion directories: ${CC_CHECKED_IN_DIRS[@]}"
    
    
    pre_exit
    fail $ec
}

function svnlook_changed {
    exec_cmd "$SVNLOOK_CMD" changed -t "$TXN" "$SVN_REPO_DIR"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
}

function svnlook_diff {
    exec_cmd "$SVNLOOK_CMD" diff --no-diff-deleted -t "$TXN" "$SVN_REPO_DIR"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
}

function svncc_compare {
    "$SVNLOOK_CMD" cat "$SVN_REPO_DIR" "$1" | "$CMP_CMD" "$2" &>> "$LOG_FILE"
    if [[  $? != 0 || ${PIPESTATUS[((${#PIPESTATUS[@]} - 2))]} != 0 ]] 
    then
        log "Files HEAD/$1 and $2 are not identical"
        rollback_and_fail
    fi
}

function svncc_cat {
    log "cat $1 > $2"
    "$SVNLOOK_CMD" cat -t "$TXN" "$SVN_REPO_DIR" "$1" > "$2" 2>> "$LOG_FILE"
    [[  $? != 0 ]] && rollback_and_fail
}

function cc_pre_committ {
     if [ ! -z "$CC_PRE_COMMITT_FILE_CMD" ] 
     then
         exec_cmd "$CC_PRE_COMMITT_FILE_CMD" "$1"
         [[  $EXIT_CODE != 0 ]] && rollback_and_fail
     fi
}

function cc_co_file {
    cc co -unreserved -c "$COMMENT" "$1"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
    CC_CHECKED_OUT_FILES[${#CC_CHECKED_OUT_FILES[@]}]="$1"
}

function cc_co_dir {
    if [[ `array_contains CC_CHECKED_OUT_DIRS "$1"` == false ]]
    then
        cc co -unreserved -c "$COMMENT" "$1"
        [[  $EXIT_CODE != 0 ]] && rollback_and_fail
        CC_CHECKED_OUT_DIRS[${#CC_CHECKED_OUT_DIRS[@]}]="$1"
    fi
}

function cc_add_file {
    local parent_dir="`dirname "$1"`/"
    cc_co_dir "$parent_dir"
    [ ! -f "$1" ] && touch "$1"
    cc mkelem -c "$COMMENT" "$1"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
    CC_CHECKED_OUT_FILES[${#CC_CHECKED_OUT_FILES[@]}]="$1"
    CC_CREATED_FILES[${#CC_CREATED_FILES[@]}]="$1"
}

function cc_add_dir {
    local parent_dir="`dirname "$1"`/"
    cc_co_dir "$parent_dir"
    cc mkdir -c "$COMMENT" "$1"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
    CC_CHECKED_OUT_DIRS[${#CC_CHECKED_OUT_DIRS[@]}]="$1"
    CC_CREATED_DIRS[${#CC_CREATED_DIRS[@]}]="$1"
}

function cc_delete {
    local parent_dir="`dirname "$1"`/"
    cc_co_dir "$parent_dir"
    cc rmname -force -c "$COMMENT" "$1"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
    CC_DELETED[${#CC_DELETED[@]}]="$1"
    rm -rf "$1"
}
################################################################################

############################## Collect changes #################################
log "Collecting changes"
CHANGES=`svnlook_changed`

ORIG_IFS=$IFS
IFS=$'\n'
for line in $CHANGES
do
    log "$line"
    case ${line:0:2} in
        "U " | "UU") SVN_CHANGED[${#SVN_CHANGED[@]}]=`trim "${line:2}"` ;;
        "D ") SVN_DELETED[${#SVN_DELETED[@]}]=`trim "${line:2}"` ;;
        "A ")
            f=`trim "${line:2}"`

            if [[ $f == */ ]]
            then
                SVN_NEW_DIRS[${#SVN_NEW_DIRS[@]}]="$f"
            else
                SVN_NEW_FILES[${#SVN_NEW_FILES[@]}]="$f"
            fi
            ;;
    esac
done
IFS=$ORIG_IFS
################################################################################

#################### Compare changed files with svn HEAD #######################
size=${#SVN_CHANGED[@]}
[[ $size != 0 ]] && log "Verifying files to be changed: ${SVN_CHANGED[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    f1="${SVN_CHANGED[$i]}"
    f2=`cc_view_path "${f1#*"$SVN_REPO_ROOT/"}"`
    svncc_compare "$f1" "$f2"
    CC_TOBE_CHANGED[${#CC_TOBE_CHANGED[@]}]="$f2"
done
################################################################################

############## Check that files to be deleted exist in ClearCase ###############
size=${#SVN_DELETED[@]}
[[ $size != 0 ]] && log "Verifying files to be deleted: ${SVN_DELETED[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    f=`cc_view_path "${SVN_DELETED[$i]#*"$SVN_REPO_ROOT/"}"`
    [ ! -e "$f" ] && log "File not found: $f" && rollback_and_fail
    CC_TOBE_DELETED[${#CC_TOBE_DELETED[@]}]="$f"
done
################################################################################

########### Check that files to be added do not exist in ClearCase #############
size=${#SVN_NEW_FILES[@]}
[[ $size != 0 ]] && log "Verifying new files: ${SVN_NEW_FILES[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    f=`cc_view_path "${SVN_NEW_FILES[$i]#*"$SVN_REPO_ROOT/"}"`
    [ -e "$f" ] && log "File already exists: $f" && rollback_and_fail
    CC_TOBE_CREATED_FILES[${#CC_TOBE_CREATED_FILES[@]}]="$f"
done
################################################################################

######## Check that directories to be added do not exist in ClearCase ##########
size=${#SVN_NEW_DIRS[@]}
[[ $size != 0 ]] && log "Verifying new directories: ${SVN_NEW_DIRS[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    f=`cc_view_path "${SVN_NEW_DIRS[$i]#*"$SVN_REPO_ROOT/"}"`
    [ -e "$f" ] && log "File already exists: $f" && rollback_and_fail
    CC_TOBE_CREATED_DIRS[${#CC_TOBE_CREATED_DIRS[@]}]="$f"
done
################################################################################

########################## Check out files to be changed #######################
size=${#CC_TOBE_CHANGED[@]}
[[ $size != 0 ]] && log "Checking out files to be changed: ${CC_TOBE_CHANGED[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    cc_co_file "${CC_TOBE_CHANGED[$i]}"
done
################################################################################

############################ Create new directories in CC ######################
size=${#CC_TOBE_CREATED_DIRS[@]}
[[ $size != 0 ]] && log "Creating new directories: ${CC_TOBE_CREATED_DIRS[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    cc_add_dir "${CC_TOBE_CREATED_DIRS[$i]}"
done
################################################################################

######################### Copy new files to CC view ############################
size=${#SVN_NEW_FILES[@]}
[[ $size != 0 ]] && log "Creating new files: ${CC_TOBE_CREATED_FILES[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    svncc_cat "${SVN_NEW_FILES[$i]}" "${CC_TOBE_CREATED_FILES[$i]}"
done
################################################################################

######################### Add new files to ClearCase ###########################
size=${#CC_TOBE_CREATED_FILES[@]}
[[ $size != 0 ]] && log "Adding new files to ClearCase: ${CC_TOBE_CREATED_FILES[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    cc_add_file "${CC_TOBE_CREATED_FILES[$i]}"
done
################################################################################

############################ Delete files ######################################
size=${#CC_TOBE_DELETED[@]}
[[ $size != 0 ]] && log "Deleting files: ${CC_TOBE_DELETED[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    cc_delete "${CC_TOBE_DELETED[$i]}"
done
################################################################################

####################### Copy changed files to ClearCase ########################
size=${#SVN_CHANGED[@]}
[[ $size != 0 ]] && log "Changing files: ${CC_TOBE_CHANGED[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    svncc_cat "${SVN_CHANGED[$i]}" "${CC_TOBE_CHANGED[$i]}"
done
################################################################################

######################### Check in checked out files ###########################
size=${#CC_CHECKED_OUT_FILES[@]}
[[ $size != 0 ]] && log "Checking in files: ${CC_CHECKED_OUT_FILES[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    f=${CC_CHECKED_OUT_FILES[$i]}
    cc_pre_committ "$f"
    cc ci -c "$COMMENT" "$f"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
    CC_CHECKED_IN_FILES[${#CC_CHECKED_IN_FILES[@]}]="$f"
    unset CC_CHECKED_OUT_FILES[$i]
done
################################################################################

###################### Check in checked out directories ########################
size=${#CC_CHECKED_OUT_DIRS[@]}
[[ $size != 0 ]] && log "Checking in directories: ${CC_CHECKED_OUT_DIRS[@]}"
for (( i = 0 ; i < $size; i++ ))
do
    f=${CC_CHECKED_OUT_DIRS[$i]}
    cc ci -c "$COMMENT" "$f"
    [[  $EXIT_CODE != 0 ]] && rollback_and_fail
    CC_CHECKED_IN_DIRS[${#CC_CHECKED_IN_DIRS[@]}]="$f"
    unset CC_CHECKED_OUT_DIRS[$i]
done
################################################################################

pre_exit
success 
